package abacab.remote.oio;

import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import abacab.remote.Connection;
import abacab.remote.ConnectionRegistry;

/**
 * A client for a socket connection
 */
public class SocketClient extends Connection {
	/** The logger */
	private static final Logger	logger	= LoggerFactory.getLogger(SocketClient.class);

	/** The hostname */
	private final String				hostPort;

	/** The connection's socket */
	private final Socket				socket;

	/** The writer */
	private final SocketWriter	writer;

	/** The reader */
	private final SocketReader	reader;

	/**
	 * Create the client
	 * 
	 * @param registry
	 *          the registry
	 * @param hostname
	 *          the hostname
	 * @param port
	 *          the port
	 * @throws ConnectException
	 *           if an error occurred
	 */
	public SocketClient(ConnectionRegistry registry, String hostname, int port) throws ConnectException {
		super(registry);
		try {
			this.socket = new Socket(hostname, port);
			register();
			this.writer = new SocketWriter(this, this.socket);
			this.reader = new SocketReader(this, this.socket);
		}
		catch (Exception e) {
			if (this.socket != null) {
				try {
					this.socket.close();
				}
				catch (IOException e1) {
					logger.warn("Failed to close a socket", e1);
				}
				throw new ConnectException("Could not write to the socket");
			}
			logger.error("Could not connect", e);
			throw new ConnectException("Could not connect");
		}
		this.hostPort = hostname + ":" + port;
	}

	/**
	 * Create the client
	 * 
	 * @param registry
	 *          the registry
	 * @param socket
	 *          the socket
	 * @throws ConnectException
	 *           if an error occurred
	 */
	public SocketClient(ConnectionRegistry registry, Socket socket) throws ConnectException {
		super(registry);
		try {
			this.socket = socket;
			register();
			this.writer = new SocketWriter(this, this.socket);
			this.reader = new SocketReader(this, this.socket);
		}
		catch (Exception e) {
			if (this.socket != null) {
				try {
					this.socket.close();
				}
				catch (IOException e1) {
					logger.warn("Failed to close a socket", e1);
				}
				throw new ConnectException("Could not write to the socket");
			}
			logger.error("Could not connect", e);
			throw new ConnectException("Could not connect");
		}
		InetSocketAddress address = getRemoteAddress();
		this.hostPort = address.getHostName() + ":" + address.getPort();
	}

	@Override
	public String getRemoteHostPort() {
		return this.hostPort;
	}

	@Override
	public final InetSocketAddress getRemoteAddress() {
		return (InetSocketAddress) this.socket.getRemoteSocketAddress();
	}

	@Override
	public void close() {
		unregister();
		this.writer.stop();
		this.reader.stop();
		try {
			this.socket.close();
		}
		catch (IOException e) {
			logger.warn("Could not close the connection");
		}
	}

	@Override
	public void send(byte[] message) {
		this.writer.send(message);
	}
}